﻿using System.Net;
using Microsoft.Extensions.Options;

namespace Journeo_backend.Configuration
{
    public interface IProxyPool
    {
        IWebProxy GetRandomProxy();
        void MarkProxyAsFailed(string proxyAddress);
        void ReloadProxies(List<ProxyConfig> proxies);
    }

    public class ProxyPool : IProxyPool
    {
        private readonly List<ProxyStatus> _proxies;
        private readonly Random _random;
        private readonly ILogger<ProxyPool> _logger;
        private readonly SemaphoreSlim _lock = new SemaphoreSlim(1, 1);

        public ProxyPool(IOptions<ProxyPoolOptions> options, ILogger<ProxyPool> logger)
        {
            _logger = logger;
            _proxies = options.Value.Proxies.Select(p => new ProxyStatus(p)).ToList();
            _random = new Random();
        }

        public IWebProxy GetRandomProxy()
        {
            _lock.Wait();
            try
            {
                var availableProxies = _proxies.Where(p => !p.IsFailed).ToList();
                if (!availableProxies.Any())
                {
                    // 如果所有代理都失败，重置状态
                    foreach (var proxy in _proxies)
                    {
                        proxy.ResetFailedStatus();
                    }
                    availableProxies = _proxies;
                }

                var proxyStatus = availableProxies[_random.Next(availableProxies.Count)];
                var config = proxyStatus.Config;

                var proxyUri = new Uri($"{(config.Type == ProxyType.HTTP ? "http" : "socks5")}://{config.Host}:{config.Port}");

                var webProxy = new WebProxy()
                {
                    Address = proxyUri,
                    BypassProxyOnLocal = false,
                    UseDefaultCredentials = false
                };

                if (!string.IsNullOrEmpty(config.Username) && !string.IsNullOrEmpty(config.Password))
                {
                    var credentials = new NetworkCredential(config.Username, config.Password);
                    webProxy.Credentials = credentials;

                    _logger.LogInformation($"Configured proxy {config.Host}:{config.Port} with authentication");
                }
                else
                {
                    _logger.LogInformation($"Using proxy {config.Host}:{config.Port} without authentication");
                }

                return webProxy;
            }
            finally
            {
                _lock.Release();
            }
        }

        public void MarkProxyAsFailed(string proxyAddress)
        {
            _lock.Wait();
            try
            {
                var proxy = _proxies.FirstOrDefault(p =>
                    $"{p.Config.Host}:{p.Config.Port}" == proxyAddress);

                if (proxy != null)
                {
                    proxy.MarkAsFailed();
                    _logger.LogWarning($"Proxy {proxyAddress} marked as failed");
                }
            }
            finally
            {
                _lock.Release();
            }
        }

        public void ReloadProxies(List<ProxyConfig> proxies)
        {
            _lock.Wait();
            try
            {
                _proxies.Clear();
                _proxies.AddRange(proxies.Select(p => new ProxyStatus(p)));
            }
            finally
            {
                _lock.Release();
            }
        }

        private class ProxyStatus
        {
            public ProxyConfig Config { get; }
            public bool IsFailed { get; private set; }
            public DateTime? FailedTime { get; private set; }

            public ProxyStatus(ProxyConfig config)
            {
                Config = config;
            }

            public void MarkAsFailed()
            {
                IsFailed = true;
                FailedTime = DateTime.UtcNow;
            }

            public void ResetFailedStatus()
            {
                IsFailed = false;
                FailedTime = null;
            }
        }
    }
}
